import { VTree, SSMLRenderNode, SSMLProvider } from './type'
import { parseDocument } from 'htmlparser2'
import { multiplePinyin } from '../pinyin-pro'
// import Mitt from '@/utils/mitt'
import { getType, getDataType, checkConjunction, getMarkByAttrib, genTextNode, genRenderNode, genBreakNode, getMarkByTime, genLineBreakNode } from './utils'
import { vTree2text, vTree2ssml, vTree2Rendener, renderers2VTree } from './transform'
import { conjReg } from '../regexp'
import { MapResultItem } from '../pinyin-pro/types/type'

export class SSMLEngine {
  // export class SSMLEngine extends Mitt<'vDomChange'> {
  /** ssml虚拟节点树 */
  vdom: VTree = {
    type: 'root',
    children: [{
      attribs: {},
      type: 'tag',
      name: 'speak',
      children: [],
    }],
  }
  /**
   * ssml虚拟树引擎
   * @param ssml 标记语言
   */
  constructor(ssml?: string) {
    // super()
    this.setVDomBySSML(ssml || '')
  }
  getParseDocument(ssml: string) {
    if (!/^\s*<speak(.|\s)+<\/speak>\s*$/i.test(ssml)) {
      ssml = `<speak>${ssml}</speak>`
    }
    return parseDocument(ssml, { xmlMode: true }) as VTree
  }
  setVDomBySSML(ssml: string) {
    if (!ssml) return
    this.vdom = this.getParseDocument(ssml)
    // this.emit('vDomChange', this)
    return this
  }
  /**
   * 根据渲染队列设置vdom
   * @param renderer 渲染队列
   */
  setVDomByRenderer(renderer: SSMLRenderer) {
    const _vDom = renderers2VTree(renderer)
    this.vdom = _vDom
    // this.emit('vDomChange', this)
    return this
  }
  /**
   * 获取纯文本
   * @param options.break 可定义停顿输出为指定字符
   */
  getText(options?: { break: string }) {
    const vTreeText = vTree2text(this.vdom, options)
    return vTreeText
  }
  /**
   * 获取ssml
   * @param provider 交互统一使用默认供应商，仅在试听时根据供应商进行转换
   */
  getSSML(provider: SSMLProvider = SSMLProvider.DEFAULT) {
    const vTreeString = vTree2ssml(this.vdom, provider)
    return vTreeString
  }
  /**
   * 获取渲染模板
   */
  getRenderer() {
    const renderer = vTree2Rendener(this.vdom)
    return renderer
  }
  getCustomText(ssml: string, options: { break: string }) {
    if (!ssml) return ''
    const vdom = this.getParseDocument(ssml)
    return vTree2text(vdom, options)
  }
}

export class SSMLRenderer {
  // export class SSMLRenderer extends Mitt {
  /** 初始记录 */
  __Storage_HEAD__: any = {}
  /** 记录的存储key */
  __StorageKey__ = 'history@ssmlRender'
  /** 当前记录指向 */
  __HistoryPoint__ = -1
  /** 渲染树属性 */
  attribs: Record<string, any> = {}
  /** 节点 */
  children: Array<SSMLRenderNode> = []
  /** 当前光标 */
  cursor = 0
  /** 创建节点时的key，每次创建节点后需递增 */
  point = 0
  /** 中文拼音多音字词典 */
  multiPYDict: Record<string, Array<MapResultItem>> = {}
  /** 节点总数 */
  get total() {
    return this.children.length
  }
  /** 纯文本字数 */
  get length() {
    return this.getText(0, this.total, { break: '' }).length
  }
  /**
   * 将ssml虚拟树转换为dom的渲染队列
   * @param model 渲染节点模板
   */
  constructor(model?: Record<string, any>) {
    // super()
    this.loadModel(model)
  }
  /**
   * 记录功能初始化
   */
  initHistory() {
    this.__Storage_HEAD__ = { cursor: this.cursor, children: this.children }
    sessionStorage.setItem(this.__StorageKey__, JSON.stringify([this.__Storage_HEAD__]));
    this.__HistoryPoint__ = 0
    return this
  }
  /**
   * 获取记录存储，目前存储于 sessionStorage，关闭浏览器自动清除
   */
  getStorage() {
    const history = JSON.parse(sessionStorage.getItem(this.__StorageKey__) || '[]');
    // 若存储为空，则重新初始化记录（适用于存储被异常清除的状况）
    if (history.length === 0) {
      console.error('history')
      this.initHistory();
    }
    return history
  }
  /**
   * 重置为初始记录
   */
  resetHeadHistory() {
    this.cursor = this.__Storage_HEAD__.cursor
    this.children = this.__Storage_HEAD__.children
    return this
  }
  /**
   * 添加操作记录
   */
  pushHistory() {
    let history = this.getStorage()
    history.splice(this.__HistoryPoint__ + 1, history.length - 1 - this.__HistoryPoint__, { cursor: this.cursor, children: this.children })
    this.__HistoryPoint__ = history.length - 1
    // 超出最大记录时，自动移除最前面的记录
    if (history.length > 20) {
      history = history.slice(-21)
      this.__HistoryPoint__ = history.length - 1
    }
    sessionStorage.setItem(this.__StorageKey__, JSON.stringify(history));
    return this
  }
  /**
   * 记录前进
   */
  forwordHistory() {
    const history = this.getStorage()
    if (this.__HistoryPoint__ >= history.length - 1) return
    const historyItem = history[++this.__HistoryPoint__]
    this.cursor = historyItem.cursor
    this.children = historyItem.children
    return this
  }
  /**
   * 记录回退
   */
  backHistory() {
    const history = this.getStorage()
    if (this.__HistoryPoint__ <= 0) return
    const historyItem = history[--this.__HistoryPoint__]
    this.cursor = historyItem.cursor
    this.children = historyItem.children
    return this
  }
  /**
   * 读取渲染节点模板到渲染队列
   * @param model 渲染节点模板
   */
  loadModel(model?: Record<string, any>) {
    if (!model) return
    if (model.attribs) this.attribs = model.attribs
    if (model.children) {
      this.children = model.children
      this.cursor = this.children.length
    }
    if (model.point) this.point = model.point
    if (model.multiPYDict) this.multiPYDict = model.multiPYDict
    this.initHistory();
    return this
  }
  /**
   * 获取纯文本
   * @param start 开始位置
   * @param end 结束位置
   * @param options.break 可定义停顿输出为指定字符
   */
  getText(start: number, end: number, options?: { break: string }) {
    const text = this.children.slice(start, end).reduce((s, child) => s + (child.dataType === 'break' ? options?.break ?? ' ' : child.data), '')
    return text
  }
  /**
   * 获取ssml
   * @param provider 交互统一使用默认供应商，仅在试听时根据供应商进行转换
   */
  getSSML(provider: SSMLProvider = SSMLProvider.DEFAULT) {
    const vdom = renderers2VTree(this)
    const vTreeString = vTree2ssml(vdom, provider)
    return vTreeString
  }
  /**
   * 读取中文字典
   * @param text 要读取的字符串
   */
  loadMultiPYDict(text: string) {
    if (!text.length) return
    this.multiPYDict = {
      ...this.multiPYDict,
      ...multiplePinyin(text),
    }
  }
  /**
   * 根据文本插入节点
   * @param data 要插入的文本
   * @param position 要插入的位置
   */
  insertChilds(data: string, position: number) {
    this.loadMultiPYDict(data)
    const _children: Array<SSMLRenderNode> = [];
    for (let i = 0; i < data.length; i++) {
      const nodeData = data[i]
      if (nodeData === '\n') {
        _children.push(genLineBreakNode(this.point++));
      } else {
        _children.push(genTextNode(nodeData, this.point++));
      }
    }
    this.insertOriginChilds(_children, position)
    return this
  }
  /**
   * 插入停顿
   * @param position 要插入的位置
   * @param duration 停顿规格
   */
  insertBreak(position: number, duration = '1s') {
    this.insertOriginChilds([genBreakNode(duration, this.point++)], position)
    return this
  }
  /**
   * 插入换行
   * @param position 要插入的位置
   */
  insertLineBreak(position: number) {
    this.insertOriginChilds([genLineBreakNode(this.point++)], position)
    return this
  }
  /**
   * 根据文本替换所有节点
   * @param data 要替换的文本
   */
  replaceAll(data: string) {
    const _children: Array<SSMLRenderNode> = [];
    for (let i = 0; i < data.length; i++) {
      const nodeData = data[i]
      _children.push(genTextNode(nodeData, this.point++));
    }
    this.children = _children
  }
  /**
   * 插入原始节点
   * @param nodes 要插入的节点列表
   * @param position 要插入的位置
   */
  insertOriginChilds(nodes: Array<SSMLRenderNode>, position: number) {
    this.children = [
      ...this.children.slice(0, position),
      ...nodes,
      ...this.children.slice(position),
    ]
    this.cursor += nodes.length;
    this.pushHistory()
    return this
  }
  /**
   * 移除节点
   * @param startPosition 起始位置
   * @param endPosition 结束位置
   */
  removeChilds(startPosition: number, endPosition: number) {
    this.children = [
      ...this.children.slice(0, startPosition),
      ...this.children.slice(endPosition),
    ];
    this.pushHistory()
    return this
  }
  /**
   * 节点检测并根据连词合并
   */
  checkMerge() {
    // 已检测未标记节点重新拆开
    this.children = this.children.reduce<SSMLRenderNode[]>((list, child) => {
      if (child.marked) {
        list.push(child)
      } else {
        if (child.data!.length > 1) {
          list.push(...child.data!.split('').map(t => genTextNode(t, this.point++)))
        } else {
          list.push(child)
        }
      }
      return list
    }, [])

    // 对单节点的内容进行连词检测
    const exsitConjPos: Array<number> = this.children.reduce<Array<number>>((list, child, index) => {
      conjReg.lastIndex = 0
      const isConj = conjReg.test(child.data!)
      return isConj ? [...list, index] : list
    }, [])

    // 根据连词位置，切割出单节点为非连词的区域
    for (let i = exsitConjPos.length; i >= 0; i--) {
      const start = i - 1 > -1 ? exsitConjPos[i - 1] + 1 : 0
      const end = i < exsitConjPos.length ? exsitConjPos[i] : this.total
      const textSlice = this.getText(start, end)
      if (textSlice) {
        // 读取切片，并将所有中文存在的读音存到字典（该方法会过滤非中文）
        this.loadMultiPYDict(textSlice)
        // 检测切片内多节点相邻时存在的连词
        const conjs = checkConjunction(textSlice)
        for (let k = conjs.length - 1; k >= 0; k--) {
          const conj = conjs[k]
          const nodeData = conj[0]
          const type = getType(nodeData)
          const dataType = getDataType(nodeData, type)
          // 将新生成的连词节点替换原来的多个节点，注意：由于splice会改变数组长度，因此遍历必须从后往前置换
          this.children.splice(start + conj.index, nodeData.length, genRenderNode(type, dataType, nodeData, this.point++))
        }
      }
    }

    // 对所有节点进行状态标记
    this.children.forEach(child => {
      if (!child.checked) {
        const data = child.data!
        if (child.type === 'text') child.type = getType(data)
        child.dataType = getDataType(data, child.type)
      }
      child.checked = true
      if (child.attribs) {
        if (child.attribs['interpret-as']) {
          child.mark = getMarkByAttrib(child.attribs['interpret-as'])
        }
        if (child.attribs['alphabet'] === 'py' && child.attribs.ph) {
          child.mark = this.multiPYDict[child.data!]?.find(py => py.pinyin === child.attribs!.ph)?.result
        }
        if (child.attribs['time']) {
          child.mark = getMarkByTime(child.attribs.time)
        }
      }
    })
    this.pushHistory()
    // console.log('checked:', this.children)
    return this
  }
}

export default SSMLEngine
